package io.lvdaxian.mybatis12.builder.annotation;

import io.lvdaxian.mybatis12.annotations.Delete;
import io.lvdaxian.mybatis12.annotations.Insert;
import io.lvdaxian.mybatis12.annotations.Select;
import io.lvdaxian.mybatis12.annotations.Update;
import io.lvdaxian.mybatis12.binding.MapperMethod;
import io.lvdaxian.mybatis12.builder.MapperBuilderAssistant;
import io.lvdaxian.mybatis12.mapping.SqlCommandType;
import io.lvdaxian.mybatis12.mapping.SqlSource;
import io.lvdaxian.mybatis12.scripting.LanguageDriver;
import io.lvdaxian.mybatis12.session.Configuration;
import io.lvdaxian.mybatis12.session.ResultHandler;
import io.lvdaxian.mybatis12.session.RowBounds;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * 这里是处理 注解sql 的mapper
 *
 * @author lihh
 */
public class MapperAnnotationBuilder {
  private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
  
  private Configuration configuration;
  private MapperBuilderAssistant assistant;
  private Class<?> type;
  
  public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace(".", "/") + ".java (best guess)";
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;
    
    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);
  }
  
  /**
   * 这是一个解析的入口
   *
   * @author lihh
   */
  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 如果是接口的话，就以接口的名称 就是命名空间
      assistant.setCurrentNamespace(type.getName());
      
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        // 不是 继承得到的方法
        if (!method.isBridge()) {
          // 解析语句
          parseStatement(method);
        }
      }
    }
  }
  
  private void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    
    if (sqlSource != null) {
      final String mappedStatementId = type.getName() + "." + method.getName();
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      
      String resultMapId = null;
      if (isSelect) {
        resultMapId = parseResultMap(method);
      }
      
      // 调用助手类
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          sqlCommandType,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          languageDriver
      );
    }
  }
  
  /**
   * 重点：DAO 方法的返回类型，如果为 List 则需要获取集合中的对象类型
   */
  private Class<?> getReturnType(Method method) {
    // 这里直接就是类型
    Class<?> returnType = method.getReturnType();
    
    // 如果返回的是 集合的话
    if (Collection.class.isAssignableFrom(returnType)) {
      Type returnTypeParameter = method.getGenericReturnType();
      if (returnTypeParameter instanceof ParameterizedType) {
        Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
        if (actualTypeArguments != null && actualTypeArguments.length == 1) {
          returnTypeParameter = actualTypeArguments[0];
          if (returnTypeParameter instanceof Class) {
            returnType = (Class<?>) returnTypeParameter;
          } else if (returnTypeParameter instanceof ParameterizedType) {
            // (issue #443) actual type can be a also a parameterized type
            returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
          } else if (returnTypeParameter instanceof GenericArrayType) {
            Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
            // (issue #525) support List<byte[]>
            returnType = Array.newInstance(componentType, 0).getClass();
          }
        }
      }
    }
    return returnType;
  }
  
  /**
   * 解析 结果集合
   *
   * @param method 执行的名称
   * @return 返回字符串
   * @author lihh
   */
  private String parseResultMap(Method method) {
    // generateResultMapName
    StringBuilder suffix = new StringBuilder();
    for (Class<?> c : method.getParameterTypes()) {
      suffix.append("-");
      suffix.append(c.getSimpleName());
    }
    if (suffix.length() < 1) {
      suffix.append("-void");
    }
    String resultMapId = type.getName() + "." + method.getName() + suffix;
    
    // 添加 ResultMap
    Class<?> returnType = getReturnType(method);
    assistant.addResultMap(resultMapId, returnType, new ArrayList<>());
    return resultMapId;
  }
  
  /**
   * 得到 sql 命令类型
   *
   * @param method 执行的方法
   * @return sql 命令
   * @author lihh
   */
  private SqlCommandType getSqlCommandType(Method method) {
    Class<? extends Annotation> type = getSqlAnnotationType(method);
    if (type == null) {
      return SqlCommandType.UNKNOWN;
    }
    return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
  }
  
  /**
   * 通过 注解 得到 sqlSource
   *
   * @param method         方法
   * @param parameterType  参数类型
   * @param languageDriver 语言驱动
   * @return 返回 sqlSource
   * @author lihh
   */
  private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      if (sqlAnnotationType != null) {
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      }
      return null;
    } catch (Exception e) {
      throw new RuntimeException("Could not find value method on SQL annotation.  Cause: " + e);
    }
  }
  
  
  /**
   * 通过 string 字符串 构建 sqlSource
   *
   * @param strings            字符串类型的数组
   * @param parameterTypeClass 类型 type 类
   * @param languageDriver     驱动
   * @return
   */
  private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
    final StringBuilder sql = new StringBuilder();
    for (String fragment : strings) {
      sql.append(fragment);
      sql.append(" ");
    }
    return languageDriver.createSqlSource(configuration, sql.toString(), parameterTypeClass);
  }
  
  /**
   * 这里是通过方法 拿到方法上的 sql注解
   *
   * @param method 方法名称
   * @return 注解类
   * @author lihh
   */
  private Class<? extends Annotation> getSqlAnnotationType(Method method) {
    for (Class<? extends Annotation> type : sqlAnnotationTypes) {
      Annotation annotation = method.getAnnotation(type);
      if (annotation != null) return type;
    }
    return null;
  }
  
  /**
   * 根据 方法拿到驱动
   *
   * @param method 方法本身
   * @return 从这里返回语言驱动
   * @author lihh
   */
  private LanguageDriver getLanguageDriver(Method method) {
    Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
    return configuration.getLanguageRegistry().getDriver(langClass);
  }
  
  /**
   * 返回类型的方法
   *
   * @param method 这是调用的方法
   * @return 这里返回类型
   * @author lihh
   */
  private Class<?> getParameterType(Method method) {
    Class<?> parameterType = null;
    // 从这里拿到所有的返回类型
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (Class<?> clazz : parameterTypes) {
      // RowBounds.class.isAssignableFrom(clazz) 判断 clazz 是否能赋值给 RowBounds
      // RowBounds 这里可能参数是一个分页类型
      // ResultHandler 可能参数是一个 结果处理函数
      if (!RowBounds.class.isAssignableFrom(clazz) && !ResultHandler.class.isAssignableFrom(clazz)) {
        if (parameterType == null) {
          // 就是本身的类型
          parameterType = clazz;
        } else {
          parameterType = MapperMethod.ParamMap.class;
        }
      }
    }
    return parameterType;
  }
}
